/**
 * 执行流程的同意，拒绝，取回，终止等动作
 *
 * 输入参数:
 *    InstanceID: 流程实例的ID
 *    NodeID：    流程当前节点的ID
 *    Action：    同意/拒绝/取回/终止
 *    Opinion：   意见
 *    ToStart:    拒绝和取回时，用来指定是否回到开始节点
 *
 * 输出参数:
 *
 */
(() => {
  // 同意
  function pass(UID, workflow, instance, record, opinion) {
    const cur_node = workflow.node(instance.当前节点.ID);

    // 检查是否存在当前人的待办事项
    const { rows: my_tasks } = A.db.execute('MP', "SELECT ID FROM B_LCDB WHERE LCSL_ID=? AND DBYH_ID=?", [instance.ID, UID]);
    if (my_tasks.length === 0) throw Error("您不是当前处理人");

    // 1. 处理会签的投票比例
    if (cur_node.类型 === 4) {
      // 先投票, 然后判断是否满足通过条件
      A.db.trans('MP', conn => {
        // 清理当前用户的待办任务
        A.db.execute(conn, "DELETE B_LCDB WHERE LCSL_ID=? AND DBYH_ID=?", [instance.ID, UID]);
        const log = insert_log(UID, conn, instance, cur_node, cur_node, '同意', opinion);
        // 更新流程实例的最新记录
        A.job.CardW(UID, 'B.流程实例', ['最新记录'], { ID: instance.ID, 最新记录: log.ID });
      });

      // 获取待办人数
      const { rows: tasks } = A.db.execute('MP', "SELECT ID FROM B_LCDB WHERE LCSL_ID=?", [instance.ID]);
      // 获取投票人数
      const { Rows: votes } = A.job.CardR(UID, 'B.流程记录', ['流程实例', '通过节点', '回滚标记', '动作'],
        [['流程实例', '=', instance.ID], ['通过节点', '=', instance.当前节点.ID], ['回滚标记', '=', false]]);

      const agree_rate = votes.filter(v => v.动作 === '同意').length / (tasks.length + votes.length);
      Logger.Debug(`会签状态: 会签基数: ${tasks.length + votes.length}, 同意率: ${agree_rate * 100}%, 通过率条件: ${cur_node.会签通过率 * 100}%`);
      // 如果未达成通过，直接退出，等待继续投票即可（逻辑上这里不存在未达成通过但是已全部投票的情况，因为在这之前某个拒绝动作应该已经触发会签拒绝了）
      if (agree_rate < Number(cur_node.会签通过率 || 0)) {
        return true;
      }
    }

    // 2. 选择路径, 确定下一节点。自动跳过虚拟节点
    let next_node = cur_node;
    do {
      let default_node = null;
      let max_priority = 1;
      const next_nodes = next_node.next.map(path => {
        const result = path.onValidate({ UID, workflow, instance, record });
        if (result == null) {
          if (default_node) throw Error(`找到了多个默认路径: ${default_node.名称}, ${path.node.名称}`);
          default_node = path.node;
        }
        const priority = Number(result ?? 0);
        if (isNaN(priority)) throw Error(`路径 ${path.node.名称} 的优先级必须是数字`);
        max_priority = Math.max(max_priority, priority);
        return { path, priority };
      }).filter(({ priority }) => priority === max_priority).map(({ path }) => path);
      if (next_nodes.length > 1) throw Error('找到了多个可通过路径: ' + next_nodes.map(p => p.node.名称).join(','));
      next_node = next_nodes.length > 0 ? next_nodes[0].node : default_node;
      if (!next_node) throw Error("没有找到可通过路径");
    } while (next_node.类型 === 5);

    // 3. 确定待办用户
    const userIds = Array.from(new Set(next_node.onGetUsers({ UID, workflow, instance, record }) || []));
    if (next_node.类型 !== 2 && userIds.length === 0) throw Error(`没有找到下个节点${next_node.名称}的待办用户`);

    // 4. 执行流转
    A.db.trans('MP', conn => {
      // 执行当前节点的离开脚本
      cur_node.onLeave({ UID, workflow, instance, record, leave: cur_node, arrive: next_node, action: '同意', forward: true });
      // 清理当前节点的所有待办任务
      A.db.execute(conn, "DELETE B_LCDB WHERE LCSL_ID=?", [instance.ID]);
      // 更新流程实例
      if (cur_node.类型 === 4) { // 会签节点已经更新了最新记录，只需要更新流程节点
        A.job.CardW(UID, 'B.流程实例', ['当前节点', '结束标记'], {
          ID: instance.ID, 当前节点: next_node.ID, 结束标记: next_node.类型 === 2
        });
      } else { // 非会签节点要产生流程记录
        const log = insert_log(UID, conn, instance, cur_node, next_node, (cur_node.类型 === 1 ? '提交' : '同意'), opinion);
        // 更新流程实例状态
        A.job.CardW(UID, 'B.流程实例', ['当前节点', '结束标记', '最新记录'], {
          ID: instance.ID, 当前节点: next_node.ID, 结束标记: next_node.类型 === 2, 最新记录: log.ID
        });
      }
      // 创建新节点的待办任务
      if (next_node.类型 !== 2) {
        userIds.forEach(id => {
          A.card['B.流程待办'].save({
            流程实例: instance.ID,
            待办用户: id,
            描述: next_node.onGetDesc({ UID, workflow, instance, record }),
            发起用户: instance.创建用户,
            发起日期: instance.创建时间,
          }, UID);
        });
      }
      // 执行后续节点的到达脚本
      next_node.onArrive({ UID, workflow, instance, record, userIds, leave: cur_node, arrive: next_node, action: '同意', forward: true });
    });

    // 5. 二次审批自动流转
    if (next_node.二审自动通过) {
      const { Rows: logs } = A.job.CardR(UID, 'B.流程记录', ['创建用户', '流程实例', '回滚标记'],
        [['流程实例', '=', instance.ID], ['回滚标记', '=', false]]);
      userIds.some(id => {
        if (logs.find(log => log.创建用户 === id)) {
          A.job['B.Flow_Action']({
            UID: id,
            InstanceID: instance.ID,
            NodeID: next_node.ID,
            Action: '同意',
            Opinion: "已同意自动通过",
          });
          if (next_node.类型 !== 4) return true; // 非会签节点，一次调用就流转了，就不需要检查后续用户了
        }
      });
    }

    return { ...next_node, prev: undefined, next: undefined };
  }

  // 拒绝
  function reject(UID, workflow, instance, record, opinion, toStart) {
    const cur_node = workflow.node(instance.当前节点.ID);

    // 检查是否存在当前人的待办事项
    const { rows: my_tasks } = A.db.execute('MP', "SELECT ID FROM B_LCDB WHERE LCSL_ID=? AND DBYH_ID=?", [instance.ID, UID]);
    if (my_tasks.length === 0) throw Error("您不是当前处理人");

    // 1. 处理会签的投票比例
    if (cur_node.类型 === 4) {
      // 先投票, 然后判断是否满足退回条件
      A.db.trans('MP', conn => {
        // 清理当前用户的待办任务
        A.db.execute(conn, "DELETE B_LCDB WHERE LCSL_ID=? AND DBYH_ID=?", [instance.ID, UID]);
        const log = insert_log(UID, conn, instance, cur_node, cur_node, '拒绝', opinion);
        // 更新流程实例的最新记录
        A.job.CardW(UID, 'B.流程实例', ['最新记录'], { ID: instance.ID, 最新记录: log.ID });
      });

      // 获取待办人数
      const { rows: tasks } = A.db.execute('MP', "SELECT ID FROM B_LCDB WHERE LCSL_ID=?", [instance.ID]);
      // 获取投票人数
      const { Rows: votes } = A.job.CardR(UID, 'B.流程记录', ['流程实例', '通过节点', '回滚标记', '动作'],
        [['流程实例', '=', instance.ID], ['通过节点', '=', instance.当前节点.ID], ['回滚标记', '=', false]]);

      const refuse_rate = votes.filter(v => v.动作 === '拒绝').length / (tasks.length + votes.length);
      Logger.Debug(`会签状态: 会签基数: ${tasks.length + votes.length}, 拒绝率: ${refuse_rate * 100}%, 通过率条件: ${cur_node.会签通过率 * 100}%`);
      // 如果未达成拒绝，直接退出，等待继续投票即可（逻辑上这里不存在未达成拒绝但是已全部投票的情况，因为在这之前某个同意动作应该已经触发会签通过了）
      if (refuse_rate < 1 - Number(cur_node.会签通过率 || 0)) {
        return true;
      }
    }

    // 2. 通过流程记录识别出上游有效节点
    let target_log;
    const { Rows: logs } = A.job.CardR(UID, 'B.流程记录', ['流程实例', '通过节点', '通过节点.类型', '回滚标记', '序号'], {
      Filter: [['流程实例', '=', instance.ID], ['回滚标记', '=', false]],
      Order: { '序号': 'desc' },
    });
    if (toStart) {
      target_log = logs.pop();
    } else {
      if (cur_node.类型 === 4) { // 如果当前是会签节点，要排除会签记录才能找到上游节点
        target_log = logs.filter(log => log['通过节点.类型'] !== 4).shift();
      } else {
        target_log = logs.shift();
      }
    }
    const next_node = workflow.node(target_log.通过节点);

    // 3. 确定待办用户
    const userIds = next_node.类型 === 1 ? [instance.创建用户] : Array.from(new Set(next_node.onGetUsers({ UID, workflow, instance, record }) || []));
    if (userIds.length === 0) throw Error(`没有找到上个节点${next_node.名称}的待办用户`);

    // 4. 执行流转
    A.db.trans('MP', conn => {
      // 执行当前节点的离开脚本
      cur_node.onLeave({ UID, workflow, instance, record, leave: cur_node, arrive: next_node, action: '拒绝', forward: false });
      // 清理当前节点的所有待办任务
      A.db.execute(conn, "DELETE B_LCDB WHERE LCSL_ID=?", [instance.ID]);
      // 更新流程实例
      if (cur_node.类型 === 4) { // 会签节点已经更新了最新记录，只需要更新流程节点
        A.job.CardW(UID, 'B.流程实例', ['当前节点'], {
          ID: instance.ID, 当前节点: next_node.ID
        });
      } else { // 非会签节点要产生流程记录
        const log = insert_log(UID, conn, instance, cur_node, next_node, '拒绝', opinion);
        // 更新流程实例状态
        A.job.CardW(UID, 'B.流程实例', ['当前节点', '最新记录'], {
          ID: instance.ID, 当前节点: next_node.ID, 最新记录: log.ID
        });
      }
      // 回滚受影响的流程记录
      A.db.execute(conn, "UPDATE B_LCJL SET HGBJ=1 WHERE LCSL_ID=? AND XH>=? AND HGBJ=0", [instance.ID, target_log.序号]);

      // 创建新节点的待办任务
      userIds.forEach(id => {
        A.card['B.流程待办'].save({
          流程实例: instance.ID,
          待办用户: id,
          描述: next_node.onGetDesc({ UID, workflow, instance, record }),
          发起用户: instance.创建用户,
          发起日期: instance.创建时间,
        }, UID);
      });
      // 执行后续节点的到达脚本
      next_node.onArrive({ UID, workflow, instance, record, userIds, leave: cur_node, arrive: next_node, action: '拒绝', forward: false });
    });

    return { ...next_node, prev: undefined, next: undefined };
  }

  // 取回
  function retract(UID, workflow, instance, record, opinion, toStart) {
    const cur_node = workflow.node(instance.当前节点.ID);
    if (cur_node.类型 === 2) throw Error('不允许取回已经结束的流程');

    // 1. 倒序加载所有提交记录
    const { Rows: logs } = A.job.CardR(UID, 'B.流程记录', ['流程实例', '回滚标记', '创建用户', '通过节点', '到达节点', '序号'], {
      Filter: [['流程实例', '=', instance.ID], ['回滚标记', '=', false]],
      Order: { '序号': 'desc' },
    });
    if (logs.length === 0) throw Error('未能找到提交记录');

    // 2. 确定可取回记录
    let last = toStart ? logs.pop() : logs.shift();
    if (last.通过节点 === last.到达节点) { // 会签场景，只可以取回自己的会签记录
      if (last.创建用户 !== UID) {
        last = logs.filter(l => l.通过节点 === l.到达节点 && l.到达节点 === last.到达节点 && l.创建用户 === UID)[0];
        if (!last) throw Error('未能找到可取回的提交记录');
      }
    } else {
      if (last.创建用户 !== UID) throw Error('未能找到可取回的提交记录');
    }
    const next_node = workflow.node(last.通过节点);

    // 3. 执行取回
    if (last.通过节点 === last.到达节点) { // 取回会签记录
      A.db.trans('MP', conn => {
        // 创建新的取回记录
        const log = insert_log(UID, conn, instance, cur_node, next_node, '取回', opinion);
        // 更新流程实例的最新记录
        A.job.CardW(UID, 'B.流程实例', ['最新记录'], { ID: instance.ID, 最新记录: log.ID });
        // 只回滚要取回的会签记录，其他会签记录依然有效
        A.db.execute(conn, "UPDATE B_LCJL SET HGBJ=1 WHERE ID IN (?,?)", [log.ID, last.ID]);
        // 生成新的待办
        A.card['B.流程待办'].save({
          流程实例: instance.ID,
          待办用户: UID,
          描述: next_node.onGetDesc({ UID, workflow, instance, record }),
          发起用户: instance.创建用户,
          发起日期: instance.创建时间,
        }, UID);
        // 如果是已达成的会签，还需要流转回来
        if (last.到达节点 !== instance.当前节点.ID) {
          // 执行当前节点的离开脚本
          cur_node.onLeave({ UID, workflow, instance, record, leave: cur_node, arrive: next_node, action: '取回', forward: false });
          // 更新流程实例状态
          A.job.CardW(UID, 'B.流程实例', ['当前节点'], { ID: instance.ID, 当前节点: next_node.ID });
          // 执行后续节点的到达脚本
          next_node.onArrive({ UID, workflow, instance, record, userIds: [UID], leave: cur_node, arrive: next_node, action: '取回', forward: false });
        }
      });
    } else { // 取回非会签记录时，都需要流转
      // 确定待办用户
      const userIds = next_node.类型 === 1 ? [instance.创建用户] : Array.from(new Set(next_node.onGetUsers({ UID, workflow, instance, record }) || []));
      if (userIds.length === 0) throw Error(`没有找到取回节点${next_node.名称}的待办用户`);

      // 执行流转
      A.db.trans('MP', conn => {
        // 执行当前节点的离开脚本
        cur_node.onLeave({ UID, workflow, instance, record, leave: cur_node, arrive: next_node, action: '取回', forward: false });
        // 清理当前节点的所有待办任务
        A.db.execute(conn, "DELETE B_LCDB WHERE LCSL_ID=?", [instance.ID]);
        // 生成取回记录
        const log = insert_log(UID, conn, instance, cur_node, next_node, '取回', opinion);
        // 更新流程实例状态
        A.job.CardW(UID, 'B.流程实例', ['当前节点', '最新记录'], { ID: instance.ID, 当前节点: next_node.ID, 最新记录: log.ID });
        // 回滚受影响的流程记录
        A.db.execute(conn, "UPDATE B_LCJL SET HGBJ=1 WHERE LCSL_ID=? AND XH>=? AND HGBJ=0", [instance.ID, last.序号]);
        // 创建新节点的待办任务
        userIds.forEach(id => {
          A.card['B.流程待办'].save({
            流程实例: instance.ID,
            待办用户: id,
            描述: next_node.onGetDesc({ UID, workflow, instance, record }),
            发起用户: instance.创建用户,
            发起日期: instance.创建时间,
          }, UID);
        });
        // 执行后续节点的到达脚本
        next_node.onArrive({ UID, workflow, instance, record, userIds, leave: cur_node, arrive: next_node, action: '取回', forward: false });
      });
    }

    return { ...next_node, prev: undefined, next: undefined };
  }

  // 终止
  function abort(UID, workflow, instance, record, opinion) {
    const cur_node = workflow.node(instance.当前节点.ID);
    const next_node = workflow.end;

    A.db.trans('MP', conn => {
      // 执行当前节点的离开脚本
      cur_node.onLeave({ UID, workflow, instance, record, leave: cur_node, arrive: next_node, action: '终止', forward: true });
      // 清理当前节点的所有待办任务
      A.db.execute(conn, "DELETE B_LCDB WHERE LCSL_ID=?", [instance.ID]);
      // 生成新的记录
      const log = insert_log(UID, conn, instance, cur_node, next_node, '终止', opinion);
      // 更新流程实例状态
      A.job.CardW(UID, 'B.流程实例', ['当前节点', '结束标记', '最新记录'], {
        ID: instance.ID, 当前节点: next_node.ID, 结束标记: true, 最新记录: log.ID
      });
      // 执行后续节点的到达脚本
      next_node.onArrive({ UID, workflow, instance, record, userIds: [], leave: cur_node, arrive: next_node, action: '终止', forward: true });
    });

    return { ...next_node, prev: undefined, next: undefined };
  }

  // 创建流程记录
  function insert_log(UID, conn, instance, origin_node, target_node, action, opinion) {
    // 获取流程记录的最大序号
    const { rows: [max = {}] } = A.db.execute(conn, "SELECT MAX(XH) XH FROM B_LCJL WHERE LCSL_ID=?", [instance.ID]);
    // 创建流程记录
    const log = {
      ID: A.guid(),
      回滚标记: false,
      流程实例: instance.ID,
      通过节点: origin_node.ID,
      到达节点: target_node.ID,
      动作: action,
      意见: opinion,
      序号: (max.XH || 0) + 1,
    };
    A.card['B.流程记录'].save(log, UID);
    return log;
  }

  return function ({ UID, InstanceID, NodeID, Action, Opinion, ToStart }) {
    // 验证输入参数
    if (!UID) throw Error("无效会话");
    if (!InstanceID) throw Error("缺失流程实例参数");
    if (!NodeID) throw Error("缺失当前节点参数");
    if (!Action) throw Error("缺失动作参数");

    // 去抖动处理：同一个实例、节点和用户，1秒钟内只可以请求1次
    if (A.cache.get(`workflow-${InstanceID}-${NodeID}-${UID}`)) return true;
    A.cache.set(`workflow-${InstanceID}-${NodeID}-${UID}`, true, 1);

    // 1. 获取实例信息, 检查当前节点是否匹配
    const instance = A.card['B.流程实例'].load(InstanceID);
    if (!instance) throw Error('流程实例不存在');
    if (NodeID !== instance.当前节点.ID) throw Error("提交的节点已经流转");

    // 2. 加载流程定义
    const workflow = A.job['B.Flow_LoadDefinition']({ UID, WorkflowID: instance.流程定义.ID });

    // 3. 加载业务数据
    const { rows: [pack] } = A.db.execute('KERNEL', "SELECT DM FROM KFB WHERE ID=?", [instance.流程定义.数据卡片.开发包.ID]);
    const full_name = (pack.DM ? pack.DM + '.' : '') + instance.流程定义.数据卡片.名称;
    const record = A.card[full_name].load(instance.数据记录);

    // 4. 执行动作
    switch (Action) {
      case '提交':
      case '同意': return pass(UID, workflow, instance, record, Opinion);
      case '拒绝': return reject(UID, workflow, instance, record, Opinion, ToStart);
      case '取回': return retract(UID, workflow, instance, record, Opinion, ToStart);
      case '终止': return abort(UID, workflow, instance, record, Opinion);
    }
  };
})()